/*
 *	Chemical equation to mathematic equations converter
 *
 *	Copyright (C) 2011 XiaoJSoft Studio. All Rights Reserved.
 *	Copyright (C) Ji WenCong <whs_jwc@163.com>
 *
 *	This program is free software: you can redistribute it and/or modify
 *	it under the terms of the GNU General Public License as published by
 *	the Free Software Foundation, either version 3 of the License, or
 *	(at your option) any later version.
 *
 *	This program is distributed in the hope that it will be useful,
 *	but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *	GNU General Public License for more details.
 *
 *	You should have received a copy of the GNU General Public License
 *	along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <math.h>
#include <malloc.h>
#include "../include/mmdiv.h"
#include "../include/fraction.h"
#include "../include/interpreter.h"
#include "../include/strext.h"
#include "../include/equation.h"

/*
 *	element* analyzeElements(char *szEqu, element *source, int *count);
 *
 *	Analyze the composition of the chemical
 *
 *	*szEqu	->	the chemical equation of the material,
 *	*source	->	the origin elements heap,
 *	*count	->	the depth of the origin elements heap.
 */
element* analyzeElements(char *szEqu, element *source, int *count)
{
	char *proc = NULL;
	int dx, dy;
	element *solve;
	int solveCount;
	char *leftExpr = NULL, *midExpr = NULL, *rightExpr = NULL, *rightExpr2 = NULL;
	long double suffix;
	char *midExpr2 = NULL;
	long double pfx2;
	int cancelAdd;
	char *oinEqu = NULL;
	long double pfx;
	element *ret;
	int ptrUC;
	int idx;
	long double ecount = 0;
	char *ename = NULL;
	char *reinput;
	collection *eachSide = NULL;
	collection *selItem = NULL;

	if (szEqu == NULL) {
		return(NULL);
	}

	ret = source;

	/*  Is the chemical a hydrate?  */
	if (strin_ext_expr(1, szEqu, ".") != 0) {
		/*  Separate the chemical equation  */
		eachSide = sliceString_expr(szEqu, ".");
		if (eachSide == NULL) {
			return(NULL);
		}

		for (idx = 1; idx <= getCollectionCount(eachSide); idx++) {
			/*  Processing each part of the chemical equation independently  */
			selItem = getCollectionItem(eachSide, idx);
			if (selItem == NULL) {
				free(eachSide);
				return(NULL);
			}
			ret = analyzeElements(selItem->string, ret, count);
			if (ret == NULL) {
				free(eachSide);
				return(NULL);
			}
		}

		freeCollection(eachSide);

		return(ret);
	}

	/*  To get the prefix number of the chemical equation  */
	pfx = strtod_ext(szEqu, &oinEqu);

	if (strlen_ext(oinEqu) == strlen_ext(szEqu)) {
		pfx = 1;
	}

	oinEqu = strgleft_ext(oinEqu, strlen_ext(oinEqu));
	if (oinEqu == NULL) {
		return(NULL);
	}

	/*  Solving the brackets  */
	while(!0) {
		/*  Looking for the first bracket in the chemical equation  */
		dx = strin_ext(1, oinEqu, "(");
		if (dx < 1) {
			break;
		}

		/*  To get the back of the first bracket  */
		dy = strin_ext_expr(dx, oinEqu, ")");
		if (dy < dx) {
			free(oinEqu);
			return(NULL);
		}

		/*  To get the expressions besides the bracket  */
		leftExpr = strgleft_ext(oinEqu, dx - 1);
		if (leftExpr == NULL) {
			free(oinEqu);
			return(NULL);
		}

		rightExpr = strgright_ext(oinEqu, strlen_ext(oinEqu) - dy);
		if (rightExpr == NULL) {
			free(leftExpr);
			free(oinEqu);
			return(NULL);
		}

		/*  To get the expressions inside the bracket  */
		midExpr = strmid_ext(oinEqu, dx + 1, dy - dx - 1);
		if (midExpr == NULL) {
			free(leftExpr);
			free(rightExpr);
			free(oinEqu);
			return(NULL);
		}

		/*  Support for ionization chemical equations  */
		pfx2 = strtod_ext(midExpr, &midExpr2);
		if (strlen_ext(midExpr) == strlen_ext(midExpr2)) {
			pfx2 = 1;
		}

		suffix = strtod_ext(rightExpr, &rightExpr2);

		if (strlen_ext(rightExpr) == strlen_ext(rightExpr2)) {
			suffix = 1;
		}

		if (strcmp_ext(midExpr2, "e+") == 1 || strcmp_ext(midExpr2, "e-") == 1) {
			solveCount = 1;
			solve = (element*) malloc(1 * sizeof(element));
			if (solve == NULL) {
				free(midExpr);
				free(oinEqu);
				free(leftExpr);
				free(rightExpr);
				return(NULL);
			}
			solve->name = (char*) malloc(2 * sizeof(char));
			if (solve->name == NULL) {
				free(solve);
				free(midExpr);
				free(oinEqu);
				free(leftExpr);
				free(rightExpr);
				return(NULL);
			}

			/*  Replace with "e"  */
			*(solve->name) = 'e';
			*(solve->name+1) = '\0';
			solve->count = pfx2;

			/*  The electronic number  */
			if (strcmp_ext(midExpr2, "e-") == 1) {
				solve->count = -solve->count;
			}
		} else {
			/*  Analyze the expression normally  */
			solveCount = 0;
			solve = analyzeElements(midExpr, NULL, &solveCount);
		}
		free(midExpr);
		free(oinEqu);

		if (solve == NULL) {
			free(leftExpr);
			free(rightExpr);
			return(NULL);
		}

		/*  Connecting two expressions  */
		kasprintf(&oinEqu, "%s%s", leftExpr, rightExpr2);
		free(leftExpr);
		free(rightExpr);

		if (oinEqu == NULL) {
			preFreeElement(solve, solveCount);
			free(solve);
			return(NULL);
		}

		/*  Add the elements into main elements heap  */
		for (dx = 1; dx <= solveCount; dx++)
		{
			cancelAdd = 0;
			ename = (solve + dx - 1)->name;
			ecount = (solve + dx - 1)->count;
			for (idx = 1; idx <= *count; idx++) {
				if (strcmp_ext(ename, (ret + idx - 1)->name) == 1) {
					(ret + idx - 1)->count = (ret + idx - 1)->count + ecount * suffix;
					cancelAdd = 1;
					break;
				}
			}
			if (cancelAdd == 1) {
				continue;
			}
			ret = (element*) realloc(ret, (++(*count)) * sizeof(element));
			if (ret == NULL) {
				free(oinEqu);
				return(NULL);
			}
			(ret + *count - 1)->name = strgleft_ext(ename, strlen_ext(ename));
			(ret + *count - 1)->count = ecount * suffix;
		}
		preFreeElement(solve, solveCount);
		free(solve);
	}
	while(!0) {
		if (oinEqu == NULL) {
			return(NULL);
		}

		if (strlen_ext(oinEqu) <= 0) {
			free(oinEqu);
			break;
		}

		/*  Scanning the first upcase character in the expression  */
		ptrUC = strlen_ext(oinEqu);
		for (idx = 2; idx <= strlen_ext(oinEqu); idx++) {
			if ((*(oinEqu + idx - 1)) >= 'A' && (*(oinEqu + idx - 1)) <= 'Z') {
				ptrUC = idx - 1;
				break;
			}
		}

		/*  To get the string contained the element name & atom count  */
		proc = strgleft_ext(oinEqu, ptrUC);
		if (proc == NULL) {
			free(oinEqu);
			return(NULL);
		}

		/*  To get the element name (ename) & atom count (ecount)  */
		for (idx = 1; idx <= (ptrUC + 1); idx++) {
			if (isNumeric(proc + idx - 1) == 1) {
				ecount = strtod_ext(proc + idx - 1, NULL);
				ecount = (strlen_ext(proc + idx - 1) == 0) ? (long double) 1 : ecount;
				ename = strgleft_ext(proc, idx - 1);
				if (ename == NULL) {
					free(oinEqu);
					free(proc);
					return(NULL);
				}
				if (strlen_ext(ename) == 0) {
					free(oinEqu);
					free(proc);
					return(NULL);
				}
				break;
			}
		}

		free(proc);

		/*  Is the element exists?  */
		for (idx = 1; idx <= *count; idx++) {
			if (strcmp_ext(ename, (ret + idx - 1)->name) == 1) {
				free((ret + idx - 1)->name);
				(ret + idx - 1)->name = ename;
				(ret + idx - 1)->count = (ret + idx - 1)->count + ecount * pfx;
				goto existElement;
			}
		}

		/*  Reallocating enough memory areas to contain new element  */
		ret = (element*) realloc(ret, (++(*count)) * sizeof(element));
		if (ret == NULL) {
			free(oinEqu);
			return(NULL);
		}

		/*  Add the new element into main elements heap  */
		(ret + *count - 1)->count = ecount * pfx;
		(ret + *count - 1)->name = ename;
existElement:
		/*  Generating new expression  */
		if (ptrUC == strlen_ext(oinEqu)) {
			free(oinEqu);
			break;
		} else {
			reinput = strgright_ext(oinEqu, strlen_ext(oinEqu) - ptrUC);
			if (reinput == NULL) {
				preFreeElement(ret, *count);
				free(ret);
				free(oinEqu);
				return(NULL);
			}
			free(oinEqu);
			oinEqu = reinput;
			reinput = NULL;
		}
	}

	/*  To fix some bugs in the 'ionization chemical equation' function  */
	for (idx = 1; idx <= *count; idx++) {
		if (strcmp_ext((ret + idx - 1)->name, "e+") == 1) {
			free((ret + idx - 1)->name);
			(ret + idx - 1)->name = (char*) malloc(2 * sizeof(char));
			if ((ret + idx - 1)->name == NULL) {
				preFreeElement(ret, *count);
				free(ret);
			}
			*((ret + idx - 1)->name) = 'e';
			*((ret + idx - 1)->name + 1) = '\0';
		}
		if (strcmp_ext((ret + idx - 1)->name, "e-") == 1) {
			free((ret + idx - 1)->name);
			(ret + idx - 1)->name = (char*) malloc(2 * sizeof(char));
			if ((ret + idx - 1)->name == NULL) {
				preFreeElement(ret, *count);
				free(ret);
			}
			*((ret + idx - 1)->name) = 'e';
			*((ret + idx - 1)->name + 1) = '\0';
			(ret + idx - 1)->count = -(ret + idx - 1)->count;
		}
	}

	return(ret);
}

/*
 *	estack* getElementStack(char *szEquation)
 *
 *	To convert the chemical equation into mathematic equation.
 *
 *	*szEquation	->	the chemical equation.
 */
estack* getElementStack(char *szEquation)
{
	int lpCount;
	int dx, dy;
	element *nfsTry1, *nfsTry2, *nfsRealTry;
	int lpTryCount1, lpTryCount2;
	int lpRealCount1, lpRealCount2;
	int lpRealAllocate;
	int lpCurrent;
	int lpScan1, lpScan2;
	int lpSwitch;
	long double *skStack, *skConstants;
	int lpSignedX, lpSignedY;
	long double lpFill;
	char *szTemp1, *szTemp2;
	collection *spSides;
	collection *spSideA, *spSideB;
	collection *spFromA, *spFromB;
	collection *spCurrent;
	estack *nsfBuild;
	element *nfsTemp1;

	/*  Cutting the chemical equation into two halves  */
	spSides = sliceString_expr(szEquation, "=");
	if (spSides == NULL) {
		return(NULL);
	}

	if (getCollectionCount(spSides) != 2) {
		freeCollection(spSides);
		return(NULL);
	}

	spSideA = getCollectionItem(spSides, 1);
	spSideB = getCollectionItem(spSides, 2);
	if (spSideA == NULL || spSideB == NULL) {
		freeCollection(spSides);
		return(NULL);
	}

	/*  To get reactants & products  */
	spFromA = sliceString_expr(spSideA->string, "+");
	if (spFromA == NULL) {
		freeCollection(spSides);
		return(NULL);
	}
	spFromB = sliceString_expr(spSideB->string, "+");
	if (spFromB == NULL) {
		freeCollection(spFromA);
		freeCollection(spSides);
		return(NULL);
	}
	nfsTry1 = NULL; nfsTry2 = NULL;

	/*  Analyze each chemical  */
	lpCount = 0;
	for (lpCurrent = 1; lpCurrent <= getCollectionCount(spFromA); lpCurrent++) {
		spCurrent = getCollectionItem(spFromA, lpCurrent);
		if (spCurrent == NULL) {
			freeCollection(spFromA);
			freeCollection(spFromB);
			freeCollection(spSides);
			return(NULL);
		}
		nfsTry1 = analyzeElements(spCurrent->string, nfsTry1, &lpCount);
		if (nfsTry1 == NULL) {
			freeCollection(spFromA);
			freeCollection(spFromB);
			freeCollection(spSides);
			return(NULL);
		}
		spCurrent = NULL;
	}
	lpRealCount1 = lpCount;
	lpTryCount1 = lpCount;
	for (lpCurrent = 1; lpCurrent <= lpCount; lpCurrent++) {
		if (strcmp_ext((nfsTry1 + lpCurrent - 1)->name, "e") == 1) {
			lpTryCount1--;
			break;
		}
	}
	lpCount = 0;
	for (lpCurrent = 1; lpCurrent <= getCollectionCount(spFromB); lpCurrent++) {
		spCurrent = getCollectionItem(spFromB, lpCurrent);
		if (spCurrent == NULL) {
			preFreeElement(nfsTry1, lpRealCount1);
			free(nfsTry1);
			freeCollection(spFromA);
			freeCollection(spFromB);
			freeCollection(spSides);
			return(NULL);
		}
		nfsTry2 = analyzeElements(spCurrent->string, nfsTry2, &lpCount);
		if (nfsTry2 == NULL) {
			preFreeElement(nfsTry1, lpRealCount1);
			free(nfsTry1);
			freeCollection(spFromA);
			freeCollection(spFromB);
			freeCollection(spSides);
			return(NULL);
		}
		spCurrent = NULL;
	}
	lpRealCount2 = lpCount;
	lpTryCount2 = lpCount;
	for (lpCurrent = 1; lpCurrent <= lpCount; lpCurrent++) {
		if (strcmp_ext((nfsTry2 + lpCurrent - 1)->name, "e") == 1) {
			lpTryCount2--;
			break;
		}
	}

	/*  Is it a correct chemical equation?  */
	if (lpTryCount1 != lpTryCount2) {
		preFreeElement(nfsTry1, lpRealCount1);
		preFreeElement(nfsTry2, lpRealCount2);
		free(nfsTry1);
		free(nfsTry2);
		freeCollection(spFromA);
		freeCollection(spFromB);
		freeCollection(spSides);
		return(NULL);
	}

	/*  Deeply check  */
	for (lpScan1 = 1; lpScan1 <= lpRealCount1; lpScan1++) {
		lpSwitch = 0;
		szTemp1 = (nfsTry1 + lpScan1 - 1)->name;
		if (strcmp_ext(szTemp1, "e") == 1) {
			continue;
		}
		for (lpScan2 = 1; lpScan2 <= lpRealCount2; lpScan2++) {
			szTemp2 = (nfsTry2 + lpScan2 - 1)->name;
			if (strcmp_ext(szTemp2, "e") == 1) {
				continue;
			}
			if (strcmp_ext(szTemp1, szTemp2) == 1) {
				lpSwitch = 1;
				break;
			}
		}
		if (lpSwitch == 0) {
			preFreeElement(nfsTry1, lpRealCount1);
			preFreeElement(nfsTry2, lpRealCount2);
			free(nfsTry1);
			free(nfsTry2);
			freeCollection(spFromA);
			freeCollection(spFromB);
			freeCollection(spSides);
			return(NULL);
		}
	}

	/*  Allocating enough memory areas to contain new equations  */
	lpRealAllocate = (lpRealCount1 > lpRealCount2) ? lpRealCount1 : lpRealCount2;
	nfsRealTry = (lpRealCount1 > lpRealCount2) ? nfsTry1 : nfsTry2;
	skStack = (long double*) malloc(((lpRealAllocate + 1) * (getCollectionCount(spFromA) + getCollectionCount(spFromB))) * sizeof(long double));
	skConstants = (long double*) malloc((lpRealAllocate + 1) * sizeof(long double));

	if (skStack == NULL || skConstants == NULL) {
		preFreeElement(nfsTry1, lpRealCount1);
		preFreeElement(nfsTry2, lpRealCount2);
		free(nfsTry1);
		free(nfsTry2);

		freeCollection(spFromA);
		freeCollection(spFromB);
		freeCollection(spSides);

		if (skStack != NULL) {
			free(skStack);
		}

		if (skConstants != NULL) {
			free(skConstants);
		}

		return(NULL);
	}

	/*  Generating new mathematic equations  */
	lpSignedX = getCollectionCount(spFromA) + getCollectionCount(spFromB);
	lpSignedY = lpRealAllocate + 1;

	for (lpScan1 = 1; lpScan1 <= lpSignedX * lpSignedY; lpScan1++) {
		*(skStack + lpScan1 - 1) = 0;
	}

	for (lpScan1 = 1; lpScan1 <= lpSignedY; lpScan1++) {
		*(skConstants + lpScan1 - 1) = 0;
	}

	for (lpCurrent = 1; lpCurrent <= getCollectionCount(spFromA); lpCurrent++) {
		lpCount = 0;

		spCurrent = getCollectionItem(spFromA, lpCurrent);
		if (spCurrent == NULL) {
			preFreeElement(nfsTry1, lpRealCount1);
			preFreeElement(nfsTry2, lpRealCount2);
			free(nfsTry1);
			free(nfsTry2);
			freeCollection(spFromA);
			freeCollection(spFromB);
			free(skStack);
			free(skConstants);
			freeCollection(spSides);
			return(NULL);
		}

		nfsTemp1 = analyzeElements(spCurrent->string, NULL, &lpCount);
		if (nfsTemp1 == NULL) {
			preFreeElement(nfsTry1, lpRealCount1);
			preFreeElement(nfsTry2, lpRealCount2);
			free(nfsTry1);
			free(nfsTry2);
			freeCollection(spFromA);
			freeCollection(spFromB);
			free(skStack);
			free(skConstants);
			freeCollection(spSides);
			return(NULL);
		}

		for (lpScan1 = 1; lpScan1 <= lpCount; lpScan1++) {
			lpFill = (nfsTemp1 + lpScan1 - 1)->count;
			szTemp1 = (nfsTemp1 + lpScan1 - 1)->name;
			for (lpScan2 = 1; lpScan2 <= lpRealAllocate; lpScan2++) {
				szTemp2 = (nfsRealTry + lpScan2 - 1)->name;
				if (strcmp_ext(szTemp1, szTemp2) == 1) {
					*(skStack + getPositionXY(lpCurrent, lpScan2, lpSignedX) - 1) = lpFill;
					break;
				}
			}
		}

		free(nfsTemp1);
		spCurrent = NULL;
	}
	for (lpCurrent = 1; lpCurrent <= getCollectionCount(spFromB); lpCurrent++) {
		lpCount = 0;

		spCurrent = getCollectionItem(spFromB, lpCurrent);
		if (spCurrent == NULL) {
			preFreeElement(nfsTry1, lpRealCount1);
			preFreeElement(nfsTry2, lpRealCount2);
			free(nfsTry1);
			free(nfsTry2);
			freeCollection(spFromA);
			freeCollection(spFromB);
			free(skStack);
			free(skConstants);
			freeCollection(spSides);
			return(NULL);
		}

		nfsTemp1 = analyzeElements(spCurrent->string, NULL, &lpCount);
		if (nfsTemp1 == NULL) {
			preFreeElement(nfsTry1, lpRealCount1);
			preFreeElement(nfsTry2, lpRealCount2);
			free(nfsTry1);
			free(nfsTry2);
			freeCollection(spFromA);
			freeCollection(spFromB);
			free(skStack);
			free(skConstants);
			freeCollection(spSides);
			return(NULL);
		}

		for (lpScan1 = 1; lpScan1 <= lpCount; lpScan1++) {
			lpFill = (nfsTemp1 + lpScan1 - 1)->count;
			szTemp1 = (nfsTemp1 + lpScan1 - 1)->name;
			for (lpScan2 = 1; lpScan2 <= lpRealAllocate; lpScan2++) {
				szTemp2 = (nfsRealTry + lpScan2 - 1)->name;
				if (strcmp_ext(szTemp1, szTemp2) == 1) {
					*(skStack + getPositionXY(getCollectionCount(spFromA) + lpCurrent, lpScan2, lpSignedX) - 1) = -lpFill;
					break;
				}
			}
		}

		free(nfsTemp1);
		spCurrent = NULL;
	}

	*(skStack + getPositionXY(1, lpSignedY, lpSignedX) - 1) = 1;
	*(skConstants + lpSignedY - 1) = 1;

	/*  Allocating enough memory areas to contain final mathematic equations  */
	nsfBuild = (estack*) malloc(sizeof(estack));
	if (nsfBuild == NULL) {
		preFreeElement(nfsTry1, lpRealCount1);
		preFreeElement(nfsTry2, lpRealCount2);
		free(nfsTry1);
		free(nfsTry2);
		freeCollection(spFromA);
		freeCollection(spFromB);
		free(skStack);
		free(skConstants);
		freeCollection(spSides);
		return(NULL);
	}

	nsfBuild->equations = (fact*) malloc(lpSignedX * lpSignedY * sizeof(fact));
	nsfBuild->constants = (fact*) malloc(lpSignedY * sizeof(fact));
	if (nsfBuild->equations == NULL || nsfBuild->constants == NULL) {
		if (nsfBuild->equations != NULL) {
			free(nsfBuild->equations);
		}
		if (nsfBuild->constants != NULL) {
			free(nsfBuild->constants);
		}
		nsfBuild = NULL;
		goto qsub;
	}

	for (dy = 1; dy <= lpSignedY; dy++) {
		for (dx = 1; dx <= lpSignedX; dx++) {
			*(nsfBuild->equations + getPositionXY(dx, dy, lpSignedX) - 1) = fractionCreate(*(skStack + getPositionXY(dx, dy, lpSignedX) - 1), 1);
		}
		*(nsfBuild->constants + dy - 1) = fractionCreate(*(skConstants + dy - 1), 1);
	}

	free(skStack);
	free(skConstants);
	nsfBuild->maxX = lpSignedX;
	nsfBuild->maxY = lpSignedY;
qsub:
	preFreeElement(nfsTry1, lpRealCount1);
	preFreeElement(nfsTry2, lpRealCount2);
	free(nfsTry1);
	free(nfsTry2);
	freeCollection(spFromA);
	freeCollection(spFromB);
	freeCollection(spSides);

	return(nsfBuild);
}

/*
 *	void preFreeElement(element *nfsSource, int lpCount);
 *
 *	Preprocesses the elements heap which will be destroy.
 *
 *	*nfsSource	->	the pointer pointed to the elements heap which will be destroy,
 *	lpCount		->	the depth of the elements heap.
 */
void preFreeElement(element *nfsSource, int lpCount)
{
	int lpCurrent;
	for (lpCurrent = 1; lpCurrent <= lpCount; lpCurrent++) {
		if ((nfsSource + lpCurrent - 1)->name != NULL) {
			free((nfsSource + lpCurrent - 1)->name);
			(nfsSource + lpCurrent - 1)->name = NULL;
		}
	}
}

